/**********************************************************
 * ActionScript Development Tool 
 * Copyright (C) 2005 asdt.org 
 * 
 * http://www.asdt.org
 * http://sourceforge.net/projects/aseclipseplugin/
 *
 * This program is free software; 
 * you can redistribute it and/or modify it under the terms of 
 * the GNU General Public License as published by the 
 * Free Software Foundation; either version 2 of the License, 
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the 
 * Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 
 * 
 **********************************************************/
package ca.scotthyndman.as2haxe.dom;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import ca.scotthyndman.as2haxe.dom.visitor.IVisitable;
import ca.scotthyndman.as2haxe.dom.visitor.IVisitor;

/**
 * This object represents an anonymous functions or a method.
 * 
 * @author Peter Schreiber (Original)
 * @author Scott Hyndman (AS2Haxe modifications)
 */
public class ASFunction extends ASTNode implements IVisitable, IScopeElement {

	/**
	 * The "name" structural property of this node type.
	 */
	public static final ASChildPropertyDescriptor NAME_PROPERTY = new ASChildPropertyDescriptor(
			ASFunction.class, "name", ASSimpleName.class, true);

	/**
	 * The "returnType" structural property of this node type
	 */
	public static final ASChildPropertyDescriptor RETURN_TYPE_PROPERTY = new ASChildPropertyDescriptor(
			ASFunction.class, "returnType", ASType.class, true);

	/**
	 * The "body" structural property of this node type
	 */
	public static final ASChildPropertyDescriptor BODY_PROPERTY = new ASChildPropertyDescriptor(
			ASFunction.class, "body", ASBlock.class, true);

	/**
	 * The "modifiers" structural property of this node type
	 */
	public static final ASSimplePropertyDescriptor MODIFIERS_PROPERTY = new ASSimplePropertyDescriptor(
			ASFunction.class, "modifiers", int.class, true);

	/**
	 * The "constructor" structural property of this node type
	 */
	public static final ASSimplePropertyDescriptor CONSTRUCTOR_PROPERTY = new ASSimplePropertyDescriptor(
			ASFunction.class, "constructor", boolean.class, true);

	/**
	 * The "getter" structural property of this node type
	 */
	public static final ASSimplePropertyDescriptor GETTER_PROPERTY = new ASSimplePropertyDescriptor(
			ASFunction.class, "getter", boolean.class, true);

	/**
	 * The "setter" structural property of this node type
	 */
	public static final ASSimplePropertyDescriptor SETTER_PROPERTY = new ASSimplePropertyDescriptor(
			ASFunction.class, "setter", boolean.class, true);

	/**
	 * The "parameters" structural property of this node type.
	 */
	public static final ASChildListPropertyDescriptor PARAMETERS_PROPERTY = new ASChildListPropertyDescriptor(
			ASFunction.class, "parameters", ASVariableDeclaration.class);

	//
	// ======== MEMBERS
	//

	/** The function's name. */
	private ASName name;

	/** <code>true</code> if this is a constructor */
	private boolean isConstructor;

	/** <code>true</code> if this represents a property getter/setter */
	private boolean isGetter;

	/** <code>true</code> if this represents a property getter/setter */
	private boolean isSetter;

	/** modifiers for this function */
	private int modifiers = ASModifier.NONE;

	/** function parameters */
	private List<ASTNode> parameters = new ASTNode.NodeList(PARAMETERS_PROPERTY);

	/** return type for this function */
	private ASType returnType;

	/** function contents */
	private ASBlock body;

	//
	// ======== SCOPE INFORMATION
	//

	private List<ASVariableDeclaration> scopedVariables = new ArrayList<ASVariableDeclaration>();

	private List<ASFunction> scopedFunctions = new ArrayList<ASFunction>();

	//
	// ======== CONSTRUCTION
	//

	/**
	 * Constructs a new function.
	 * 
	 * @param ast
	 */
	public ASFunction(AST ast) {
		super(ast);

		isConstructor = false;
	}

	/**
	 * Returns the name of this function. This value is <code>null</code> if
	 * the function is anonymous.
	 * 
	 * @return the function's name, or <code>null</code>
	 */
	public ASName getName() {
		return name;
	}

	/**
	 * Sets the name of the function. <code>name</code> can be
	 * <code>null</code> if the function is anonymous.
	 * 
	 * @param name
	 *            the new name
	 */
	public void setName(ASName name) {
		if (name == null) {
			throw new IllegalArgumentException();
		}

		ASTNode oldChild = this.name;
		preReplaceChild(oldChild, name, BODY_PROPERTY);
		this.name = name;
		postReplaceChild(oldChild, name, BODY_PROPERTY);
	}

	/**
	 * Returns the modifiers.
	 * 
	 * @return the modifier bitmask
	 */
	public int getModifiers() {
		return modifiers;
	}

	/**
	 * Sets the modifiers for this function.
	 * 
	 * @param modifiers
	 *            the new modifier bitmask
	 */
	public void setModifiers(int modifiers) {
		preValueChange(MODIFIERS_PROPERTY);
		this.modifiers = modifiers;
		postValueChange(MODIFIERS_PROPERTY);
	}

	/**
	 * Returns the formal parameter list for this function.
	 * 
	 * @return the formal parameter list
	 */
	public List<ASTNode> getParameters() {
		return parameters;
	}

	/**
	 * Returns the return type of this function.
	 * 
	 * @return the return type
	 */
	public ASType getReturnType() {
		return returnType;
	}

	/**
	 * Sets the return type of this function.
	 * 
	 * @param returnType
	 *            the new return type
	 */
	public void setReturnType(ASType returnType) {
		if (returnType == null) {
			throw new IllegalArgumentException();
		}

		ASTNode oldChild = this.returnType;
		preReplaceChild(oldChild, returnType, BODY_PROPERTY);
		this.returnType = returnType;
		postReplaceChild(oldChild, returnType, BODY_PROPERTY);
	}

	/**
	 * Returns the block that contains this function's body.
	 * 
	 * @return the body
	 */
	public ASBlock getBody() {
		return body;
	}

	/**
	 * Sets the function's body to <code>block</code>.
	 * 
	 * @param block
	 *            the function's body
	 */
	public void setBody(ASBlock block) {
		if (block == null) {
			throw new IllegalArgumentException();
		}

		ASTNode oldChild = this.body;
		preReplaceChild(oldChild, block, BODY_PROPERTY);
		this.body = block;
		postReplaceChild(oldChild, block, BODY_PROPERTY);
	}

	/**
	 * Returns whether this function is an instance constructor.
	 * 
	 * @return <code>true</code> if this is a constructor
	 */
	public boolean isConstructor() {
		return isConstructor;
	}

	/**
	 * Sets whether this function represents an instance constructor.
	 * 
	 * @param isConstructor
	 *            <code>true</code> if this function is a constructor
	 */
	public void setConstructor(boolean isConstructor) {
		preValueChange(CONSTRUCTOR_PROPERTY);
		this.isConstructor = isConstructor;
		postValueChange(CONSTRUCTOR_PROPERTY);
	}

	/**
	 * Returns whether this function is a property getter or setter.
	 * 
	 * @return <code>true</code> if this function is a getter or a setter
	 */
	public boolean isProperty() {
		return isGetter || isSetter;
	}

	/**
	 * Returns whether this function is a getter
	 * 
	 * @return <code>true</code> is it is a getter
	 */
	public boolean isGetter() {
		return isGetter;
	}

	/**
	 * Sets whether this function is a getter.
	 * 
	 * @param isProperty
	 *            <code>true</code> if this function is a getter.
	 */
	public void setIsGetter(boolean isProperty) {
		preValueChange(GETTER_PROPERTY);
		this.isGetter = isProperty;
		postValueChange(GETTER_PROPERTY);
	}

	/**
	 * Returns whether this function is a setter.
	 * 
	 * @return <code>true</code> if this function is a setter
	 */
	public boolean isSetter() {
		return isSetter;
	}

	/**
	 * Sets whether this function is a setter.
	 * 
	 * @param isProperty
	 *            <code>true</code> if this function is a setter.
	 */
	public void setIsSetter(boolean isProperty) {
		preValueChange(SETTER_PROPERTY);
		this.isGetter = isProperty;
		postValueChange(SETTER_PROPERTY);
	}

	//
	// ======== SCOPING RELATED STUFF
	//

	public void addToScope(ASVariableDeclaration variable) {
		scopedVariables.add(variable);
	}

	public void addToScope(ASFunction function) {
		scopedFunctions.add(function);
	}

	public List<ASVariableDeclaration> getVariablesInScope() {
		return scopedVariables;
	}

	public List<ASFunction> getFunctionsInScope() {
		return scopedFunctions;
	}

	// ====== PARENT SETTING

	@Override
	public void setParent(ASTNode parent, ASPropertyDescriptor location) {
		super.setParent(parent, location);

		IScopeElement element = getNearestScope();
		if (element != null) {
			element.addToScope(this);
		}
	}

	// ====== CONVENIENCE METHODS

	/**
	 * Returns whether this function is public
	 * 
	 * @return <code>true</code> if public
	 */
	public boolean isPublic() {
		return (getModifiers() & ASModifier.PUBLIC) != 0;
	}

	/**
	 * Sets whether this function is public.
	 * 
	 * @param flag
	 *            <code>true</code> if this function should be public
	 */
	public void setPublic(boolean flag) {
		setPrivate(!flag);
	}

	/**
	 * Returns whether this function is private
	 * 
	 * @return <code>true</code> if private
	 */
	public boolean isPrivate() {
		return (getModifiers() & ASModifier.PRIVATE) != 0;
	}

	/**
	 * Sets whether this function is private.
	 * 
	 * @param isPrivate
	 *            <code>true</code> if this function is private
	 */
	public void setPrivate(boolean isPrivate) {
		if (isPrivate) {
			this.modifiers |= ASModifier.PRIVATE;
			this.modifiers &= ~ASModifier.PUBLIC;
		} else {
			this.modifiers |= ASModifier.PUBLIC;
			this.modifiers &= ~ASModifier.PRIVATE;
		}
	}

	/**
	 * Returns whether this function is static.
	 * 
	 * @return <code>true</code> if this function is static
	 */
	public boolean isStatic() {
		return (getModifiers() & ASModifier.STATIC) != 0;
	}

	/**
	 * Sets whether this function is static.
	 * 
	 * @param isStatic
	 *            <code>true</code> if the function should be static
	 */
	public void setStatic(boolean isStatic) {
		if (isStatic) {
			this.modifiers |= ASModifier.STATIC;
		} else {
			this.modifiers &= ~ASModifier.STATIC;
		}
	}

	//
	// ======== INTERNAL GETTER SETTER
	//

	@Override
	int internalGetSetIntProperty(ASSimplePropertyDescriptor property,
			boolean get, int value) {
		if (property == MODIFIERS_PROPERTY) {
			if (get) {
				return getModifiers();
			} else {
				setModifiers(value);
			}
		}
		return super.internalGetSetIntProperty(property, get, value);
	}

	@Override
	boolean internalGetSetBooleanProperty(ASSimplePropertyDescriptor property,
			boolean get, boolean value) {
		if (property == CONSTRUCTOR_PROPERTY) {
			if (get) {
				return isConstructor();
			} else {
				setConstructor(value);
				return false;
			}
		}
		if (property == GETTER_PROPERTY) {
			if (get) {
				return isGetter();
			} else {
				setIsGetter(value);
				return false;
			}
		}
		if (property == SETTER_PROPERTY) {
			if (get) {
				return isSetter();
			} else {
				setIsSetter(value);
				return false;
			}
		}
		return super.internalGetSetBooleanProperty(property, get, value);
	}

	@Override
	ASTNode internalGetSetChildProperty(ASChildPropertyDescriptor property,
			boolean get, ASTNode child) {
		if (property == NAME_PROPERTY) {
			if (get) {
				return getName();
			} else {
				setName((ASSimpleName) child);
				return null;
			}
		}
		if (property == BODY_PROPERTY) {
			if (get) {
				return getBody();
			} else {
				setBody((ASBlock) child);
				return null;
			}
		}
		if (property == RETURN_TYPE_PROPERTY) {
			if (get) {
				return getReturnType();
			} else {
				setReturnType((ASType) child);
				return null;
			}
		}
		return super.internalGetSetChildProperty(property, get, child);
	}

	@Override
	List<ASTNode> internalGetChildListProperty(
			ASChildListPropertyDescriptor property) {
		if (property == PARAMETERS_PROPERTY) {
			return getParameters();
		}

		return super.internalGetChildListProperty(property);
	}

	// ======= OVERRIDES AND VISITOR

	/**
	 * Returns a string representation of the object.
	 */
	@Override
	public String toString() {
		StringBuffer buf = new StringBuffer();
		buf.append(name == null ? "function" : name.toString()).append('(');
		if (!parameters.isEmpty()) {
			for (Iterator<ASTNode> iter = parameters.iterator(); iter.hasNext();) {
				ASVariableDeclaration element = (ASVariableDeclaration) iter
						.next();
				if (element.getName() != null) {
					buf.append(element.getName().toString());
				}
				if (element.getType() != null) {
					buf.append(':').append(
							element.getType().getName().toString());
				}
				if (iter.hasNext()) {
					buf.append(", ");
				}
			}
		}
		buf.append(")");
		if (returnType != null) {
			buf.append(':').append(getReturnType().toString());
		}
		return buf.toString();

	}

	/**
	 * Determines equality with another object.
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof ASFunction) {
			ASFunction type = (ASFunction) obj;
			if (type.getName() != null && this.getName() != null
					&& type.getName().equals(this.getName())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Visitor pattern implementation.
	 * 
	 * @see ca.scotthyndman.as2haxe.dom.visitor.IVisitable#accept(org.asdt.core.dom.IAS2Visitor,
	 *      boolean)
	 */
	public void accept(IVisitor as2Visitor, boolean recursive) {
		recursive = as2Visitor.visit(this);
		// parameters
		if (recursive) {
			for (ASTNode var : getParameters()) {
				var.accept(as2Visitor, recursive);
			}

			if (getReturnType() != null) {
				getReturnType().accept(as2Visitor, recursive);
			}

			if (getBody() != null) {
				getBody().accept(as2Visitor, recursive);
			}
		}
	}
}